package com.hmdp.DistributedLock;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 基于Redis实现的分布式锁的接口实现类
 */

@Slf4j
public class ILockRedisImpUtil implements ILock {

    private StringRedisTemplate stringRedisTemplate;
    private static final String LOCK_PREFIX = "lock:";
    private String LockKeyName; // 服务名称
    private final String LockValue = UUID.randomUUID().toString() + "_"; // 锁的UUID的默认值
    //使用redisson客户端实现分布式锁
    @Resource
    private RedissonClient redissonClient;

    public ILockRedisImpUtil(StringRedisTemplate stringRedisTemplate, String lockKeyName) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.LockKeyName = lockKeyName;
    }

    /**
     * 尝试获取锁，成功返回true，失败返回false
     *
     * @Param timeoutSeconds 锁持有的超时时间，单位：秒,过期自动释放锁
     * @Return boolean true：获取锁成功，false：获取锁失败
     */
    @Override
    public boolean tryLock(long timeoutSeconds) {
        //拼接锁的key
        String key = LOCK_PREFIX + LockKeyName;
        String value = LockValue + Thread.currentThread().getId();
        //尝试获取锁
        Boolean isSuccess = stringRedisTemplate.opsForValue().setIfAbsent(key, value, timeoutSeconds, TimeUnit.SECONDS);
        //return BooleanUtil.isTrue(isSuccess);//源代码同下
        return Boolean.TRUE.equals(isSuccess);//防止空指针异常,除了是true,其他都返回false,是null也返回false
    }

    /**
     * 释放锁
     * 使用UUID+线程ID的方式来标识锁(保证集群环境下唯一性)，释放锁时，需要判断锁是否被当前线程持有，只有持有锁的线程才能释放锁
     */

    /*    @Override
    public void unlock() {
        //拼接锁的key
        String key = LOCK_PREFIX + LockKeyName;
        String lockIdentifyValue = LockValue + Thread.currentThread().getId();
        //获取锁的值
        String value = stringRedisTemplate.opsForValue().get(key);
        log.info("释放锁，锁的key为：{}，锁的标识值为：{}", key, lockIdentifyValue);
        log.info("从redis获取的锁的标识值为:{}", value);
        //判断锁是否被当前线程持有
        *//*判断这个锁是不是当前这个线程的,如果不是,则不能释放*//*
        if (value != null && value.equals(lockIdentifyValue)) {
            //释放锁
            stringRedisTemplate.delete(key);
            log.info("释放锁成功，锁的key为：{}", key);
        } else {
            log.error("释放锁失败，当前锁的值为:{}", value);

        }
    }
    */
    private static final DefaultRedisScript<Long> unlockScript;

    /*静态代码块用来给静态变量初始化赋值*/
    //实现静态变量的初始化
    static {
        unlockScript = new DefaultRedisScript<>();
        unlockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlock.lua")));
        unlockScript.setResultType(Long.class); // 设置脚本返回值的类型
    }

    /*静态代码块用来给静态变量初始化赋值*/

    @Override
    public void unlock() {
        //拼接锁的key
        String key = LOCK_PREFIX + LockKeyName;
        String lockIdentifyValue = LockValue + Thread.currentThread().getId();
        //使用stringRedisTemplate调用lua脚本(在类路径下面的unlock.lua脚本)实现比较锁和删除锁的原子操作
        Long executionResult = stringRedisTemplate.execute(unlockScript, Collections.singletonList(key), lockIdentifyValue);
        if (executionResult == 0) {
            log.error("释放锁失败，当前锁的值为:{}", executionResult);
        }
        log.info("释放锁成功，锁的key为：{}", key);
    }
}
